/*
 * Su-yun Information Technology, Co.,Ltd CONFIDENTIAL
 *
 * (C) Copyright Su-yun Information Technology, Co.,Ltd. 2021 All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains the property of
 * Su-yun Information Technology, Co.,Ltd. The intellectual property herein
 * are protected by trade secret, patent law or copyright law. Dissemination
 * of this information or reproduction of this material is strictly forbidden
 * unless prior written permission is obtained from Su-yun Information Technology.
 *
 * https://suyunplat.com/
 *
 */
package com.xtiim.battery.discharge.util;

import cn.hutool.core.util.NumberUtil;

import java.util.Arrays;

/**
 * Byte util to do some operation like hex conversion
 *
 */
public class ByteUtil {
    /**
     * bytes to hex string
     *
     * @param bytes
     * @return
     */
    public static String byte2hex(byte[] bytes) {
        StringBuilder sbf = new StringBuilder();
        for (byte value : bytes) {
            String hex = Integer.toHexString(value & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sbf.append(hex.toUpperCase()).append(" ");
        }
        return sbf.toString().trim();
    }

    /**
     * hex string to bytes
     *
     * @param hex
     * @return
     */
    public static byte[] hex2byte(String hex) {
        if (!isHexString(hex)) {
            return null;
        }
        char[] arr = hex.toCharArray();
        byte[] b = new byte[hex.length() / 2];
        for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) {
            String swap = "" + arr[i++] + arr[i];
            int byteint = Integer.parseInt(swap, 16) & 0xFF;
            b[j] = new Integer(byteint).byteValue();
        }
        return b;
    }

    /**
     * Answer whether is hex string
     *
     * @param hex
     * @return
     */
    public static boolean isHexString(String hex) {
        if (hex == null || hex.length() % 2 != 0) {
            return false;
        }
        for (int i = 0; i < hex.length(); i++) {
            char c = hex.charAt(i);
            if (!isHexChar(c)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Concat two bytes array
     *
     * @param bytes
     * @param additional
     * @return
     */
    public static byte[] concat(byte[] bytes, byte[] additional) {
        byte[] retBytes = new byte[bytes.length + additional.length];
        System.arraycopy(bytes, 0, retBytes, 0, bytes.length);
        System.arraycopy(additional, 0, retBytes, bytes.length, additional.length);
        return retBytes;
    }

    /**
     * Calculate XOR for section [start, end) of bytes.
     *
     * @param bytes
     * @param start
     * @param end
     * @return
     */
    public static byte xor(byte[] bytes, int start, int end) {
        if (bytes == null
                || start < 0 || start >= bytes.length
                || end < 0 || end > bytes.length) {
            throw new IllegalArgumentException("Invalid param.");
        }

        byte xor = bytes[start];
        for (int i = start + 1; i < end; i++) {
            xor ^= bytes[i];
        }

        return xor;
    }

    /**
     * convert two bytes into int
     *
     * @param byte1
     * @param byte2
     * @return
     */
    public static int toInt(byte byte1, byte byte2) {
        return Byte.toUnsignedInt(byte1) << 8 | Byte.toUnsignedInt(byte2);
    }

    /**
     * convert integer into 2-bytes array
     *
     * @param num
     * @return
     */
    public static byte[] fromInt(int num) {
        byte[] bytes = new byte[2];
        bytes[0] = (byte) ((num & 0x0000ff00) >> 8);
        bytes[1] = (byte) ((num & 0x000000ff));
        return bytes;
    }

    /**
     * Answer whether is hex char
     *
     * @param c
     * @return
     */
    private static boolean isHexChar(char c) {
        return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
    }

    /**
     * Parse double into 2-bytes array.
     * Precondition is the number follows the format:
     * integer part is 0~99
     * fraction part is 0~99
     *
     * @param number
     * @return
     */
    public static byte[] doubleToBytes(Double number) {
        byte[] bytes = new byte[2];
        String s = number.toString();
        if (s.contains(".")) {
            String[] parts = s.split("\\.");
            int intPart = NumberUtil.parseInt(parts[0]);
            bytes[0] = (byte) intPart;
            int fractionPart = NumberUtil.parseInt(parts[1]);
            bytes[1] = (byte) fractionPart;
        } else {
            bytes[0] = (byte) NumberUtil.parseInt(s);
            bytes[1] = 0;
        }
        return bytes;
    }

    /**
     * Parse 2-byte array into double
     *
     * @param bytes
     * @return
     */
    public static double bytesToDouble(byte[] bytes) {
        String intPart = String.valueOf(bytes[0]);
        String fractionPart = String.valueOf(bytes[1]);
        String number = intPart + "." + fractionPart;
        return Double.valueOf(number);
    }

    /**
     * Parse two byte into double
     *
     * @param byte1
     * @param byte2
     * @return
     */
    public static double bytesToDouble(byte byte1, byte byte2) {
        return bytesToDouble(new byte[] {byte1, byte2});
    }

    /**
     * 2-byte-BCD to double
     *
     * if floatDigit is 1 and has sign, like temperature
     * 199.9    0001 1001 1001 1001
     * -199.9   1001 1001 1001 1001
     *
     * if floatDigit is 2 and has no sign, like current or volt
     * 19.99    0001 1001 1001 1001
     * 99.99    1001 1001 1001 1001
     *
     * highest bit is sign. If has sign, precondition is next 3 bits
     * not beyond 111.
     *
     * @param byte1
     * @param byte2
     * @param floatDigit
     * @return
     */
    public static double bcd2Double(byte byte1, byte byte2, int floatDigit, boolean hasSign) {
        if (floatDigit <= 0 || floatDigit > 2) {
            throw new IllegalArgumentException("float digit support [1,2]");
        }

        boolean isNegative = false;
        if (hasSign) {
            isNegative = (Byte.toUnsignedInt(byte1) & 0x80) != 0;
        }

        byte absByte1 = hasSign ? (byte) (Byte.toUnsignedInt(byte1) & 0x7F) : byte1;
        String byte1Upper = String.valueOf((byte) ((Byte.toUnsignedInt(absByte1) & 0xF0) >>> 4));
        String byte1Lower = String.valueOf((byte) (Byte.toUnsignedInt(absByte1) & 0x0F));
        String byte2Upper = String.valueOf((byte) ((Byte.toUnsignedInt(byte2) & 0xF0) >>> 4));
        String byte2Lower = String.valueOf((byte) (Byte.toUnsignedInt(byte2) & 0x0F));

        String s = floatDigit == 1 ? (byte1Upper + byte1Lower + byte2Upper + "." + byte2Lower)
                     : byte1Upper + byte1Lower + "." + byte2Upper + byte2Lower;

        s = !isNegative ? s : "-" + s;

        return Double.valueOf(s);
    }

    /**
     * double to 2-byte-BCD
     *
     * Input value only support at most 4 effective digit,
     * like 199.9, 99.99, -199.9, -99.99
     *
     * if floatDigit is 1, like temperature,
     * 99.1 => 0000 1001 1001 0001
     * -99.1 => 1000 1001 1001 0001
     *
     * if floatDigit is 2, like current or volt
     * 99.1 => 1001 1001 0001 0000
     *
     * highest bit is sign. If has sign, precondition is next 3 bits
     * not beyond 111.
     *
     * @param value
     * @param floatDigit
     * @return
     */
    public static byte[] double2BCDOf2Bytes(double value, int floatDigit) {
        String valString = String.valueOf(value);
        String[] parts = valString.split("\\.");
        String intPart = parts[0];
        String fractionPart = parts[1];

        boolean isNegative = intPart.startsWith("-");
        String absIntPart = intPart.replace("-", "");
        int intLen = absIntPart.length();
        int fraLen = fractionPart.length();
        int totalDigit = intLen + fraLen;

        if (totalDigit > 4) {
            throw new IllegalArgumentException("Double support at most 4 digit.");
        }

        byte[] bytes = new byte[2];
        if (floatDigit == 1) {
            // integer part, hun<-ten<-unit;  fra part, tenth->hunth
            byte fra = Byte.valueOf(fractionPart.substring(0, 1));
            byte intUnit = Byte.valueOf(absIntPart.substring(intLen - 1, intLen));
            byte intTen = intLen > 1 ? Byte.valueOf(absIntPart.substring(intLen - 2, intLen - 1)) : 0;
            byte intHun = intLen > 2 ? Byte.valueOf(absIntPart.substring(intLen - 3, intLen - 2)) : 0;

            bytes[1] = (byte) (((Byte.toUnsignedInt(intUnit)) << 4) | (Byte.toUnsignedInt(fra)));
            bytes[0] = (byte) (((Byte.toUnsignedInt(intHun)) << 4) | (Byte.toUnsignedInt(intTen)));
        } else {
            // integer part, hun<-ten<-unit;  fra part, tenth->hunth
            byte fraTenth = Byte.valueOf(fractionPart.substring(0, 1));
            byte fraHunth = fraLen > 1 ? Byte.valueOf(fractionPart.substring(1, 2)) : 0;
            byte intUnit = Byte.valueOf(absIntPart.substring(intLen - 1, intLen));
            byte intTen = intLen > 1 ? Byte.valueOf(absIntPart.substring(intLen - 2, intLen - 1)) : 0;

            bytes[1] = (byte) (((Byte.toUnsignedInt(fraTenth)) << 4) | (Byte.toUnsignedInt(fraHunth)));
            bytes[0] = (byte) (((Byte.toUnsignedInt(intTen)) << 4) | (Byte.toUnsignedInt(intUnit)));
        }

        if (isNegative) {
            bytes[0] = (byte) (Byte.toUnsignedInt(bytes[0]) | 0x80);
        }

        return bytes;
    }

    /**
     * 3-byte-BCD to double
     *
     * if floatDigit is 3 and has sign, like temperature
     * 199.999    0001 1001 1001 1001 1001 1001
     * -199.999   1001 1001 1001 1001 1001 1001
     *
     * if floatDigit is 4 and has no sign, like current or volt
     * 19.9999    0001 1001 1001 1001 1001 1001
     * 99.9999    1001 1001 1001 1001 1001 1001
     *
     * @param byte1
     * @param byte2
     * @param byte3
     * @param floatDigit
     * @return
     */
    public static double bcd2Double(byte byte1, byte byte2, byte byte3, int floatDigit, boolean hasSign) {
        if (floatDigit < 3 || floatDigit > 4) {
            throw new IllegalArgumentException("float digit support [3,4]");
        }

        boolean isNegative = false;
        if (hasSign) {
            isNegative = (Byte.toUnsignedInt(byte1) & 0x80) != 0;
        }

        byte absByte1 = hasSign ? (byte) (Byte.toUnsignedInt(byte1) & 0x7F) : byte1;
        String byte1Upper = String.valueOf((byte) ((Byte.toUnsignedInt(absByte1) & 0xF0) >>> 4));
        String byte1Lower = String.valueOf((byte) (Byte.toUnsignedInt(absByte1) & 0x0F));
        String byte2Upper = String.valueOf((byte) ((Byte.toUnsignedInt(byte2) & 0xF0) >>> 4));
        String byte2Lower = String.valueOf((byte) (Byte.toUnsignedInt(byte2) & 0x0F));
        String byte3Upper = String.valueOf((byte) ((Byte.toUnsignedInt(byte3) & 0xF0) >>> 4));
        String byte3Lower = String.valueOf((byte) (Byte.toUnsignedInt(byte3) & 0x0F));

        String s = floatDigit == 3 ? (byte1Upper + byte1Lower + byte2Upper + "." + byte2Lower + byte3Upper + byte3Lower)
                     : byte1Upper + byte1Lower + "." + byte2Upper + byte2Lower + byte3Upper + byte3Lower;

        s = !isNegative ? s : "-" + s;

        return Double.valueOf(s);
    }

    /**
     * double to 3-byte-BCD
     *
     * Input value only support at most 6 effective digit,
     * like 199.999, 99.9999, -199.999, -99.9999
     *
     * if floatDigit is 3, like temperature,
     * 199.999 => 0001 1001 1001 1001 1001 1001
     * -199.999 => 1001 1001 1001 1001 1001 1001
     *
     * if floatDigit is 4, like current or volt
     * 99.9999 => 1001 1001 1001 1001 1001 1001
     *
     * @param value
     * @param floatDigit
     * @return
     */
    public static byte[] double2BCDOf3Bytes(double value, int floatDigit) {
        if (floatDigit < 3 || floatDigit > 4) {
            throw new IllegalArgumentException("float digit support [3,4]");
        }

        String valString = String.valueOf(value);
        String[] parts = valString.split("\\.");
        String intPart = parts[0];
        String fractionPart = parts[1];

        boolean isNegative = intPart.startsWith("-");
        String absIntPart = intPart.replace("-", "");
        int intLen = absIntPart.length();
        int fraLen = fractionPart.length();
        int totalDigit = intLen + fraLen;

        if (totalDigit > 6) {
            throw new IllegalArgumentException("Double support at most 6 digit.");
        }

        byte[] bytes = new byte[3];
        if (floatDigit == 3) {
            // integer part, hun<-ten<-unit;  fra part, tenth->hunth->thoth
            byte fraTen = Byte.valueOf(fractionPart.substring(0, 1));
            byte fraHun = fraLen > 1 ? Byte.valueOf(fractionPart.substring(1, 2)) : 0;
            byte fraTho = fraLen > 2 ? Byte.valueOf(fractionPart.substring(2, 3)) : 0;
            byte intUnit = Byte.valueOf(absIntPart.substring(intLen - 1, intLen));
            byte intTen = intLen > 1 ? Byte.valueOf(absIntPart.substring(intLen - 2, intLen - 1)) : 0;
            byte intHun = intLen > 2 ? Byte.valueOf(absIntPart.substring(intLen - 3, intLen - 2)) : 0;

            bytes[2] = (byte) (((Byte.toUnsignedInt(fraHun)) << 4) | (Byte.toUnsignedInt(fraTho)));
            bytes[1] = (byte) (((Byte.toUnsignedInt(intUnit)) << 4) | (Byte.toUnsignedInt(fraTen)));
            bytes[0] = (byte) (((Byte.toUnsignedInt(intHun)) << 4) | (Byte.toUnsignedInt(intTen)));
        } else {
            // integer part, ten<-unit;  fra part, tenth->hunth->thoth->ten_thoth
            byte fraTen = Byte.valueOf(fractionPart.substring(0, 1));
            byte fraHun = fraLen > 1 ? Byte.valueOf(fractionPart.substring(1, 2)) : 0;
            byte fraTho = fraLen > 2 ? Byte.valueOf(fractionPart.substring(2, 3)) : 0;
            byte fraTenTho = fraLen > 3 ? Byte.valueOf(fractionPart.substring(3, 4)) : 0;
            byte intUnit = Byte.valueOf(absIntPart.substring(intLen - 1, intLen));
            byte intTen = intLen > 1 ? Byte.valueOf(absIntPart.substring(intLen - 2, intLen - 1)) : 0;

            bytes[2] = (byte) (((Byte.toUnsignedInt(fraTho)) << 4) | (Byte.toUnsignedInt(fraTenTho)));
            bytes[1] = (byte) (((Byte.toUnsignedInt(fraTen)) << 4) | (Byte.toUnsignedInt(fraHun)));
            bytes[0] = (byte) (((Byte.toUnsignedInt(intTen)) << 4) | (Byte.toUnsignedInt(intUnit)));
        }

        if (isNegative) {
            bytes[0] = (byte) (Byte.toUnsignedInt(bytes[0]) | 0x80);
        }

        return bytes;
    }

    /**
     * BCD to 2-dec-digit positive integer
     *
     * @param bcd
     * @return
     */
    public static byte bcd2Dec(byte bcd) {
        String byte1Upper = String.valueOf((byte) ((Byte.toUnsignedInt(bcd) & 0xF0) >>> 4));
        String byte1Lower = String.valueOf((byte) (Byte.toUnsignedInt(bcd) & 0x0F));
        String s = byte1Upper + byte1Lower;

        return Byte.valueOf(s);
    }

    /**
     * 2-dec-digit positive integer to BCD
     *
     * @param dec
     * @return
     */
    public static byte dec2BCD(byte dec) {
        String intVal = String.valueOf(dec);
        int intLen = intVal.length();
        if (intLen <= 0 || intLen > 2) {
            throw new IllegalArgumentException("Support at most 2 digit integer number.");
        }

        byte intUnit = Byte.valueOf(intVal.substring(intLen - 1, intLen));
        byte intTen = intLen > 1 ? Byte.valueOf(intVal.substring(intLen - 2, intLen - 1)) : 0;

        return (byte) (((Byte.toUnsignedInt(intTen)) << 4) | (Byte.toUnsignedInt(intUnit)));
    }

    /**
     * 2-byte BCD to at most 4-digit positive integer
     *
     * @param bcd
     * @return
     */
    public static int bcd2DecInt(byte[] bcd) {
        if (bcd == null || bcd.length != 2) {
            throw new IllegalArgumentException("Support 2 byte array.");
        }

        String byteTho = String.valueOf((byte) ((Byte.toUnsignedInt(bcd[0]) & 0xF0) >>> 4));
        String byteHun = String.valueOf((byte) (Byte.toUnsignedInt(bcd[0]) & 0x0F));
        String byteTen = String.valueOf((byte) ((Byte.toUnsignedInt(bcd[1]) & 0xF0) >>> 4));
        String byteUnit = String.valueOf((byte) (Byte.toUnsignedInt(bcd[1]) & 0x0F));
        String s = byteTho + byteHun + byteTen + byteUnit;

        return Integer.valueOf(s);
    }

    /**
     * 4-dec-digit positive integer to BCD
     *
     * @param dec
     * @return
     */
    public static byte[] dec2BCD(int dec) {
        byte[] bcdBytes = new byte[2];
        Arrays.fill(bcdBytes, 0, bcdBytes.length, (byte) 0);

        String intVal = String.valueOf(dec);
        int intLen = intVal.length();
        if (intLen <= 0 || intLen > 4) {
            throw new IllegalArgumentException("Support at most 4 digit integer number.");
        }

        byte intUnit = Byte.valueOf(intVal.substring(intLen - 1, intLen));
        byte intTen = intLen > 1 ? Byte.valueOf(intVal.substring(intLen - 2, intLen - 1)) : 0;
        byte intHun = intLen > 2 ? Byte.valueOf(intVal.substring(intLen -3, intLen - 2)) : 0;
        byte intTho = intLen > 3 ? Byte.valueOf(intVal.substring(intLen -4, intLen - 3)) : 0;

        bcdBytes[1] = (byte) (((Byte.toUnsignedInt(intTen)) << 4) | (Byte.toUnsignedInt(intUnit)));
        bcdBytes[0] = (byte) (((Byte.toUnsignedInt(intTho)) << 4) | (Byte.toUnsignedInt(intHun)));

        return bcdBytes;
    }

    /**
     * BCD byte array to DEC byte array
     *
     * @param bytes
     * @return
     */
    public static byte[] bcd2Dec(byte[] bytes) {
        byte[] decBytes = new byte[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
            decBytes[i] = bcd2Dec(bytes[i]);
        }
        return decBytes;
    }

    /**
     * DEC byte array to BCD byte array
     *
     * @param bytes
     * @return
     */
    public static byte[] dec2BCD(byte[] bytes) {
        byte[] bcdBytes = new byte[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
            bcdBytes[i] = dec2BCD(bytes[i]);
        }
        return bcdBytes;
    }

    public static void main(String[] args) {

        double v1 = bcd2Double((byte) 0x19, (byte) 0x99, (byte) 0x99,3, true);
        double v2 = bcd2Double((byte) 0x19, (byte) 0x99, (byte) 0x99,4, true);

        double v3 = bcd2Double((byte) 0x99, (byte) 0x99, (byte) 0x99,3, true);
        double v4 = bcd2Double((byte) 0x99, (byte) 0x99, (byte) 0x99,4, true);
        double v5 = bcd2Double((byte) 0x99, (byte) 0x99, (byte) 0x99,3, false);
        double v6 = bcd2Double((byte) 0x99, (byte) 0x99, (byte) 0x99,4, false);

        double v7 = bcd2Double((byte) 0x00, (byte) 0x00, (byte) 0x00,4, false);

        String s = new StringBuilder().append(v1).append(",").append(v2).append(",")
                .append(v3).append(",").append(v4).append(",")
                .append(v5).append(",").append(v6).append(",")
                .append(v7)
                .toString();
        System.out.println(s);

        System.out.println(byte2hex(double2BCDOf3Bytes(199.999, 3)));
        System.out.println(byte2hex(double2BCDOf3Bytes(-199.999, 3)));
        System.out.println(byte2hex(double2BCDOf3Bytes(19.9, 3)));
        System.out.println(byte2hex(double2BCDOf3Bytes(19.9, 3)));
        System.out.println(byte2hex(double2BCDOf3Bytes(0.0, 3)));
        System.out.println(byte2hex(double2BCDOf3Bytes(99.9999, 4)));
        System.out.println(byte2hex(double2BCDOf3Bytes(-0.9999, 4)));
        System.out.println(byte2hex(double2BCDOf3Bytes(0.0, 4)));

        byte a = bcd2Dec((byte) 0x23);
        byte b = bcd2Dec((byte) 0x78);
        byte a1 = dec2BCD((byte) 23);
        byte b1 = dec2BCD((byte) 78);
        System.out.println(a + ", " + b + ", " + byte2hex(new byte[]{a1}) + ", " + byte2hex(new byte[] {b1}));
    }
}
